#pragma once
#include <limits.h>
#include <sys/stat.h>

#include <string>

#include "logger.h"

// refer https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf
template <typename... Args>
std::string strFormat(const char* format, Args... args) {
  int size_s = std::snprintf(nullptr, 0, format, args...) + 1;  // Extra space for '\0'
  if (size_s <= 0) {
    throw std::runtime_error("Error during formatting.");
  }
  auto size = static_cast<size_t>(size_s);
  std::unique_ptr<char[]> buf(new char[size]);
  std::snprintf(buf.get(), size, format, args...);
  return std::string(buf.get(), buf.get() + size - 1);  // We don't want the '\0' inside
}

int str2int(const char* in_str, int* out_val) {
  // atoi 没法区分错误和 0 值，所以用 strtol 代替
  // 参考： 1. man strtol 的 Program source
  //       2. https://stackoverflow.com/a/18544436
  long lnum;
  char* end;

  lnum = strtol(in_str, &end, 10);  // 10 specifies base-10
  if (end == in_str) {
    // if no characters were converted these pointers are equal
    LOG_FATAL("ERROR: can't convert string to number %s\n", in_str);
    return -1;
  }

  if (errno != 0 && lnum == 0) {
    LOG_FATAL("ERROR strtol failed, %s\n", strerror(errno));
    return -1;
  }

  // Because strtol produces a long, check for overflow
  if ((lnum > INT_MAX) || (lnum < INT_MIN)) {
    LOG_FATAL("ERROR: number out of range for INT\n");
    return -1;
  }

  // Finally convert the result to a plain int (if that's what you want)
  *out_val = (int)lnum;
  return 0;
}

long getFileSize(const char* filename) {
  struct stat fstat;
  if (stat(filename, &fstat) == -1) {
    LOG_FATAL("stat(filename, &fstat) != 0");
    return 0;
  }
  return (long)fstat.st_size;
}


// refer https://stackoverflow.com/a/41094722
static const int B64index[256] = {0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                                  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                                  0,  62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0,  0,  0,  0,  0,
                                  0,  0,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15, 16, 17, 18,
                                  19, 20, 21, 22, 23, 24, 25, 0,  0,  0,  0,  63, 0,  26, 27, 28, 29, 30, 31, 32, 33,
                                  34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};

std::string b64decode(const void* data, const size_t len) {
  unsigned char* p = (unsigned char*)data;
  int pad = len > 0 && (len % 4 || p[len - 1] == '=');
  const size_t L = ((len + 3) / 4 - pad) * 4;
  std::string str(L / 4 * 3 + pad, '\0');

  for (size_t i = 0, j = 0; i < L; i += 4) {
    int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | B64index[p[i + 2]] << 6 | B64index[p[i + 3]];
    str[j++] = n >> 16;
    str[j++] = n >> 8 & 0xFF;
    str[j++] = n & 0xFF;
  }
  if (pad) {
    int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12;
    str[str.size() - 1] = n >> 16;

    if (len > L + 2 && p[L + 2] != '=') {
      n |= B64index[p[L + 2]] << 6;
      str.push_back(n >> 8 & 0xFF);
    }
  }
  return str;
}